{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "slideshow": {
     "slide_type": "notes"
    }
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from IPython.core.interactiveshell import InteractiveShell\n",
    "InteractiveShell.ast_node_interactivity='all'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "<div class=\"jumbotron\">\n",
    "    <p class=\"display-1 h1\">填充和步幅</p>\n",
    "    <hr class=\"my-4\">\n",
    "    <p>主讲：李岩</p>\n",
    "    <p>管理学院</p>\n",
    "    <p>liyan@cumtb.edu.cn</p>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 填充"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### 为什么有填充的问题？"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 更大的卷积核可以更快地减小输出大小\n",
    "  - 形状从输入的$n_h × n_w$,减少到输出的$(n_h一k_h+ 1)×(n_w - k_w + 1)$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "\\begin{example}\\label{example:reducedSize}\n",
    "给定(32$\\times$ 32)输入图像，应用5$\\times$5大小的卷积核\n",
    "    \\begin{itemize}\n",
    "    \\item 第1层得到输出大小28$\\times$28\n",
    "    \\item 第7层得到输出大小4$\\times$4（因此层数不能超过7层）\n",
    "    \\end{itemize}\n",
    "\\end{example}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 如何解决网络深度受到限制？\n",
    "    - 在**输入周围添加额外的行/列**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "\\begin{definition}\\label{def:padding}\n",
    "**填充（padding）**：在输入图像的边界填充元素（通常填充元素是$0$）。\n",
    "\\end{definition}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "\\begin{example}\\label{example:padding}\n",
    "将$3 \\times 3$输入填充到$5 \\times 5$，那么它的输出就增加为$4 \\times 4$\n",
    "\\end{example}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "![](padding0.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "从这个例子中可以发现，如果没有填充，输出的形状为2\\*2，但是现在的输出是4\\*4，比输入（3*3）还大。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### 填充的方法"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 对输入填充$p_h$行（大约一半在顶部，一半在底部）和$p_w$列（左侧大约一半，右侧一半），输出形状为\n",
    "$$(n_h-k_h+p_h+1)\\times (n_w-k_w+p_w+1)$$\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 通常取$p_h = k_h-1$, $p_w=k_w-1$，以使得输出的形状和输入的形状保持一致"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "- 当$k_h$为偶数：在上下两侧填充$p_h/2$\n",
    "- 当$k_h$为奇数：在上侧填充$\\lceil{p_h/2}\\rceil$，在下侧填充$\\lfloor{p_h/2}\\rfloor$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 卷积神经网络中**卷积核的高度和宽度通常为奇数**\n",
    "    - 例如1、3、5或7"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 使用奇数的核大小和填充大小提供了书写上的便利。对于任何二维张量`X`，当满足：\n",
    "    1. 卷积核的大小是奇数；\n",
    "    2. 所有边的填充行数和列数相同；\n",
    "    3. 输出与输入具有相同高度和宽度\n",
    "\n",
    "则可以得出：输出`Y[i, j]`是通过以输入`X[i, j]`为中心，与卷积核进行互相关计算得到的"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "\\begin{example}\\label{example:paddingExample}\n",
    "在所有侧边填充1个像素的情形。当核为3$\\times$3，padding=1（上下各填充一行，左右各填充一行，即$p_h=2, p_w=2$）时，输出与输入的形状一致。\n",
    "\\end{example}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "# 为了方便起见，定义一个计算卷积层的函数。\n",
    "# 此函数初始化卷积层权重，并对输入和输出提高和缩减相应的维数\n",
    "def comp_conv2d(conv2d, X):\n",
    "    # 这里的（1，1）表示批量大小和通道数都是1\n",
    "    X = X.reshape((1, 1) + X.shape)\n",
    "    Y = conv2d(X)\n",
    "    # 省略前两个维度：批量大小和通道\n",
    "    return Y.reshape(Y.shape[2:])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[0.7175, 0.0345, 0.6503, 0.1917, 0.7842, 0.8561, 0.7978, 0.3539],\n",
       "        [0.2177, 0.0756, 0.2116, 0.4202, 0.6780, 0.1298, 0.5513, 0.6331],\n",
       "        [0.2281, 0.3408, 0.7426, 0.6783, 0.8877, 0.6139, 0.3896, 0.8620],\n",
       "        [0.7513, 0.8154, 0.9085, 0.1218, 0.2803, 0.2553, 0.6905, 0.8001],\n",
       "        [0.6058, 0.6628, 0.2646, 0.1189, 0.5386, 0.9695, 0.8801, 0.5995],\n",
       "        [0.5972, 0.7698, 0.9739, 0.4841, 0.2236, 0.6200, 0.8905, 0.7847],\n",
       "        [0.7357, 0.6533, 0.6967, 0.8520, 0.1354, 0.1894, 0.4211, 0.3424],\n",
       "        [0.5817, 0.1980, 0.1671, 0.1179, 0.6421, 0.2377, 0.4558, 0.1321]])"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "tensor([[-0.1509, -0.0978, -0.1962, -0.2682, -0.3667, -0.1798, -0.2400, -0.0960],\n",
       "        [ 0.0866,  0.0292, -0.0948, -0.0745,  0.1318,  0.3333,  0.1494,  0.0373],\n",
       "        [-0.2944, -0.3126, -0.2014,  0.1323,  0.1062,  0.0842, -0.0701,  0.0930],\n",
       "        [-0.3087, -0.0444,  0.3493,  0.4263,  0.1925, -0.0282, -0.0429,  0.2430],\n",
       "        [-0.0341,  0.2295,  0.2066,  0.0369, -0.1561, -0.1731,  0.0532,  0.2580],\n",
       "        [-0.1477, -0.0092, -0.0948, -0.0134,  0.2462,  0.2643,  0.2597,  0.2943],\n",
       "        [-0.0402,  0.3610,  0.3297,  0.2371,  0.0885,  0.2095,  0.3033,  0.3839],\n",
       "        [ 0.2380,  0.4288,  0.4207,  0.2767,  0.1050,  0.0913,  0.1604,  0.1833]],\n",
       "       grad_fn=<ReshapeAliasBackward0>)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/plain": [
       "torch.Size([8, 8])"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 请注意，这里每边都填充了1行或1列，因此总共添加了2行或2列\n",
    "conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)\n",
    "X = torch.rand(size=(8, 8)) # 生成8*8的随机张量\n",
    "X\n",
    "comp_conv2d(conv2d,X)\n",
    "comp_conv2d(conv2d, X).shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "```python\n",
    "torch.nn.Conv2d(in_channels, out_channels, kernel_size, padding=0, padding_mode='zeros')\n",
    "```\n",
    "\n",
    "- `padding`：整数、tuple或者字符串，整数表明高和宽增加相同的填充；tuple包含两个元素，分别指明宽和高的填充；如果设定为'same'，表明输出的形状与输入一致"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "- 当卷积核的高度和宽度不同时，可以[**填充不同的高度和宽度**]，使输出和输入具有相同的高度和宽度"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "\\begin{example}\\label{example:paddingDiferentSize}\n",
    "使用高度为5，宽度为3的卷积核，高度和宽度两边的填充分别为2和1。\n",
    "\\end{example}\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([8, 8])"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))\n",
    "comp_conv2d(conv2d, X).shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 步幅"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 如果说填充是防止小图片的形状随深度的增加而快速变小，那么步幅是**加快大图片的形状变小**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "\\begin{problem}\\label{prob:whyStride}\n",
    "为什么需要步幅？\n",
    "\\end{problem}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "\\begin{example}\\label{example:whyStride}\n",
    "给定输入大小224$\\times$224，在使用5$\\times$5卷积核的情况下，需要55层将输出降低到4$\\times$4\n",
    "\\end{example}\n",
    "\n",
    "需要大量计算才能得到较小输出"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 在计算互相关时，卷积窗口从输入张量的左上角开始，向下、向右滑动。\n",
    "- 前面的例子默认每次滑动一个元素。\n",
    "- 为了高效计算或是缩减采样次数，卷积窗口可以跳过中间位置，每次滑动多个元素"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "\\begin{definition}\\label{def:stride}\n",
    "步幅（stride）：行或列每次滑动元素的数量。\n",
    "\\end{definition}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "\\begin{example}\\label{example:stride}\n",
    "垂直步幅为3，水平步幅为2\n",
    "\\end{example}\n",
    "\n",
    "\n",
    "  \n",
    "  <img src='stride0.png' class='float_right h-80'>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "- 给定垂直步幅$s_h$和水平步幅$s_w$，输出形状是\n",
    "$$\\lfloor (n_h-k_h+p_h+s_h)/s_h \\rfloor \\times \\lfloor (n_w-k_w+p_w+s_w)/s_w \\rfloor$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 如果$p_h = k_h-1$, $p_w = k_w-1$\n",
    "$$\\lfloor (n_h+s_h-1)/s_h \\rfloor \\times \\lfloor (n_w+s_w-1)/s_w \\rfloor$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 如果输入高度和宽度可以被步幅整除\n",
    "$$(n_h/s_h)\\times (n_w/s_w)$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "```python\n",
    "torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)\n",
    "```\n",
    "\n",
    "- `stride`：整数或者tuple，整数表明高和宽具有相同的步幅；tuple有两个元素，分别表明高和宽的步幅"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "\\begin{example}\\label{example:strid2}\n",
    "将高度和宽度的步幅设置为2，从而将输入的高度和宽度减半。\n",
    "\\end{example}\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([4, 4])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conv2d  =  nn.Conv2d(1,1,kernel_size=3, padding=1,stride=2)\n",
    "comp_conv2d(conv2d, X).shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "\\begin{example}\\label{example:stride3}\n",
    "将高度和宽度的步幅分别设置为3和4。\n",
    "\\end{example}\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [],
   "source": [
    "conv2d = nn.Conv2d(1,1,kernel_size=(5,3),padding=(1,0),stride=(4,3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "<center><img src=\"../img/6_convolutional_neural_networks/convolutionPaddingStrides.gif\" width=60%></center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([2, 2])"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "comp_conv2d(conv2d, X).shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 总结"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- **填充和步幅是卷积层的超参数**(上节课还有一个超参数，是**卷积核的大小**)\n",
    "- 填充在输入周围添加额外的行/列，来控制输出形状的减少量\n",
    "- 步幅是每次滑动核窗口时的行/列的步长，可以成倍的减少输出形状"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 当输入高度和宽度两侧的填充数量分别为$p_h$和$p_w$时，称之为填充$(p_h, p_w)$\n",
    "- 当$p_h = p_w = p$时，填充是$p$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 当高度和宽度上的步幅分别为$s_h$和$s_w$时，称之为步幅$(s_h, s_w)$\n",
    "- 特别地，当$s_h = s_w = s$时，称步幅为$s$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "fragment"
    }
   },
   "source": [
    "- 默认情况下，填充为0，步幅为1\n",
    "- 在实践中，很少使用不一致的步幅或填充，也就是说，通常有$p_h = p_w$和$s_h = s_w$"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "幻灯片",
  "hide_input": false,
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  },
  "latex_envs": {
   "LaTeX_envs_menu_present": true,
   "autoclose": true,
   "autocomplete": true,
   "bibliofile": "biblio.bib",
   "cite_by": "apalike",
   "current_citInitial": 1,
   "eqLabelWithNumbers": true,
   "eqNumInitial": 1,
   "hotkeys": {
    "equation": "Ctrl-E",
    "itemize": "Ctrl-I"
   },
   "labels_anchors": false,
   "latex_user_defs": false,
   "report_style_numbering": false,
   "user_envs_cfg": false
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "230px"
   },
   "toc_section_display": true,
   "toc_window_display": false
  },
  "vscode": {
   "interpreter": {
    "hash": "34418ffb6f02e522390adb0e13441cc75f901cd11cccb4f6f613643b4b4d2a0b"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
